package yartiss.engine.simulation.scheduling.policies.dp;

import java.util.List;
import java.util.SortedSet;

import yartiss.engine.simulation.Event;
import yartiss.engine.simulation.IEnergyProfile;
import yartiss.engine.simulation.ISchedulingPolicy;
import yartiss.engine.simulation.events.EventGenerator;
import yartiss.engine.simulation.scheduling.policies.AbstractMonoProcessorSchedulingPolicy;
import yartiss.engine.simulation.scheduling.policies.tools.DPSlacktimeManager;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.ITaskSet;
import yartiss.engine.simulation.system.taskset.TaskSetFactory;


/**
 * Preemptive Dynamic Priority. Two threshold are setted : Etmin and Etmax.
 * When energy fail under Etmin, the system is paused for a maximal duration
 * equal to the slack time. If energy state reaches Etmax, the schedule is
 * resumed.
 */
public class EHPDP5SchedulingPolicy extends AbstractMonoProcessorSchedulingPolicy {
	private List<Integer> parameters;
	private final DPSlacktimeManager slackTimeManager;


	public EHPDP5SchedulingPolicy(DPSlacktimeManager slackTimeManager){
		this.slackTimeManager = slackTimeManager;
	}

	@Override
	public String getPolicyName() {
		return "EH_PDP_5";
	}

	@Override
	public void setParameters(List<Integer> parameters) {
		this.parameters = parameters;
	}

	@Override
	public List<Integer> getParameters() {
		return parameters;
	}

	@Override
	public ITaskSet createTaskSet() {
		return TaskSetFactory.getNewInstance("deadline");
	}

	boolean isPaused = false;

	@Override
	public ITask chooseNextTask(ITaskSet taskSet, IEnergyProfile energyProfile, long date, EventGenerator evGen) {
		getMetrics().clear();
		if (date == 0)
			isPaused = false;


		long emin = this.parameters.isEmpty() ? energyProfile.getMinEnergyLevel()	: 
			(energyProfile.getMaxEnergyLevel() - energyProfile.getMinEnergyLevel())	* parameters.get(0)	/ 100+ energyProfile.getMinEnergyLevel();

		long emax = this.parameters.isEmpty() ? energyProfile.getMaxEnergyLevel()	: 
			(energyProfile.getMaxEnergyLevel() - energyProfile.getMinEnergyLevel())	* parameters.get(1)	/ 100	+ energyProfile.getMinEnergyLevel();

		assert energyProfile.getMinEnergyLevel() <= emin && emin < emax && emax <= energyProfile.getMaxEnergyLevel() : "you should have Emin <= th_min < th_max <= Emax";

		long e = energyProfile.getCurrentEnergyLevel();

		SortedSet<ITask> activeTasks = taskSet.getActiveTasks(date);

		if (!activeTasks.isEmpty()) {
			ITask first = activeTasks.first();
			if (isPaused) {

				if (e >= emax) {

					evGen.generateEvent("check_energy_state", activeTasks.first(), date, null);
					isPaused = false;
					return null;

				}

				// e < emax

				long st = slackTimeManager.getSlackTime(date, taskSet);
				getMetrics().put(Event.SlackTime, st);
				if (st <= 0) {
					evGen.generateEvent("check_energy_state",0, activeTasks.first(), date, null);
					isPaused = false;
					return null;
				}

				// e < emax && st > 0
				long wakeUp = Math.min(date + st, date	+ (emax - e)	/ energyProfile.getPowerRate());

				if (wakeUp == date)
					wakeUp++;
				evGen.generateEvent("check_energy_state",0, activeTasks.first(), wakeUp, null);

				return null;

			}

			if (e <= emin) {

				long st = slackTimeManager.getSlackTime(date, taskSet);
				getMetrics().put(Event.SlackTime, st);
				if (st > 0) {

					// not paused, e <= emin , st > 0

					isPaused = true;

					long wakeUp = Math.min(date	+ st, date	+ (emax - e)/ energyProfile.getPowerRate());
					if (wakeUp == date)
						wakeUp++;
					evGen.generateEvent("check_energy_state",0, activeTasks.first(), wakeUp, null);

					return null;
				}

				// not paused, e <= emin , st <= 0

				long hlcet = energyProfile.howLongCanExecute(first);
				if (hlcet > 0) {
					if (hlcet < activeTasks.first().getRemainingCost())
						evGen.generateEvent("check_energy_state",0, activeTasks.first(), date+ hlcet, null);
					return activeTasks.first();
				}

				evGen.generateEvent("check_energy_state",0, activeTasks.first(), date + 1, null);
				// => de toutes facon dlm, mais peut etre pas la premiere
				// tache
				return null;
			}

			// not paused, emin < e

			long hlcet = energyProfile.howLongCanExecute(e - emin,first);

			if (hlcet > 0) {
				if (hlcet < activeTasks.first().getRemainingCost())
					evGen.generateEvent("check_energy_state", activeTasks.first(), date+ hlcet, null);
				return activeTasks.first();
			} else {
				if (e < emax) {
					isPaused = true;
					long wait = slackTimeManager.getSlackTime(date, taskSet);
					wait = Math.max(1, wait);
					wait = Math.min(wait,	(emax - e)/ energyProfile.getPowerRate());
					if (wait == 0)
						wait = 1;
					evGen.generateEvent("check_energy_state", activeTasks.first(), date+ wait, null);
				} else
					evGen.generateEvent("check_energy_state", activeTasks.first(), date + 1, null);
			}
		}
		return null;
	}
	
	@Override
	public ISchedulingPolicy newInstance() {
		return new EHPDP5SchedulingPolicy(new DPSlacktimeManager());
	}
}
